package cn.drh.charging.cost.service.impl;


import cn.drh.charging.common.pojo.param.ChargingProcessParam;
import cn.drh.charging.common.pojo.vo.ChargingProcessVO;
import cn.drh.charging.cost.dao.repostitory.CostRuleCacheRepository;
import cn.drh.charging.cost.dao.repostitory.CostRuleRepository;
import cn.drh.charging.cost.pojo.ChargingData;
import cn.drh.charging.cost.pojo.po.CostRulePO;
import cn.drh.charging.cost.service.CostService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName CostServiceImpl
 * @Description TODO
 * @Author renhong~Deng
 * @Date 2025/6/8 18:15
 * @Version 1.0
 */

@Slf4j
@Service
public class CostServiceImpl implements CostService {

    /**
     * key 订单号，value 充电数据 ，不同订单号对应不同的次数
     */
    private Map<String, ChargingData> chargingProcessData = new HashMap<>();

    /**
     * 计算价格
     *
     * @param chargingProcessParam 参数
     * @return 结果
     */
    @Override
    public ChargingProcessVO calculateCost(ChargingProcessParam chargingProcessParam) {
        log.debug("计算价格：{}", chargingProcessParam);
        //会给多次价格，每次对应的为订单号的订单进行累加
        String orderNo = chargingProcessParam.getOrderNo();
        //通过chargingProcessParam 入参订单号，获取充电数据，如果有就返回，没有就初始化并放入map
        ChargingData chargingData = getChargingData(chargingProcessParam);
        log.debug("通过订单号:{}，获取充电数据：{}", orderNo, chargingData);
        //计算价格  用户充电需要一定的时间， 整体的充电时间可能跨越多个时间段
        //1 获取计价规则
        //获取场站信息
        Integer stationId = chargingProcessParam.getStationId();
        //获取当前时间
        LocalDateTime now = LocalDateTime.now();
        int hour = getHour(now);
        //获取计价规则
        CostRulePO costRule = getMatchCostRuleByHour(stationId, hour);
        log.debug("通过当前小时：{} 获取计价规则:{}", hour, costRule);

        //2 通过计价规则计算价格
        if (costRule != null) {
            //获取计价名称
            String name = costRule.getName();
            //每度电的价格
            BigDecimal powerFee = costRule.getPowerFee();

            //判断是否为第一次
            if (chargingData.getLastChargingProcess() == null) {
                //第一次
                log.debug("第一次同步充电数据");
                firstCost2ChargingData(chargingProcessParam, chargingData, hour, name, powerFee);
            } else {
                log.debug("不是第一次同步充电数据");
                //不是第一次
                lastCost2ChargingData(chargingProcessParam, chargingData, hour, name, powerFee);
            }
        } else {
            log.error("场站信息：{}，当前时间：{}，获取计价规则失败,警告，接入全局异常处理", stationId, hour);
        }

        //3 累加
        float chargingCapacity = chargingProcessParam.getChargingCapacity();
        chargingData.setTotalCapacity(chargingCapacity);

        log.debug("当前充电进度：{}", chargingData);

        return buildChargingProcessVO(chargingData);
    }

    private ChargingProcessVO buildChargingProcessVO(ChargingData chargingData) {
        ChargingProcessVO chargingProcessVO = new ChargingProcessVO();
        //总花费
        chargingProcessVO.setTotalCost(chargingData.getTotalCost());
        //订单编号
        chargingProcessVO.setOrderNo(chargingData.getOrderNo());
        //用户id
        chargingProcessVO.setUserId(chargingData.getUserId());
        //枪id
        chargingProcessVO.setGunId(chargingData.getGunId());
        //总度数
        chargingProcessVO.setTotalCapacity(chargingData.getTotalCapacity());
        //总时长 当前时间 - 充电开始时间
        Duration between = Duration.between(chargingData.getStartTime(), LocalDateTime.now());
        String totalTime = between.toDays() + "-" + between.toHours() + "-" + between.toMinutes() + "-" + between.toMillis();
        chargingProcessVO.setTotalTime(totalTime);
        return chargingProcessVO;
    }

    /**
     * 获取当前时间-小时
     *
     * @param now 当前时间
     * @return 小时
     */
    private static int getHour(LocalDateTime now) {
        //获取当前小时
        int hour = now.getHour();
        //通过当前小时设备同步数据的时间，来获取现在的小时属于哪个时间段
        return hour;
    }

    /**
     * 第一次的充电同步的数据Param到ChargingData
     */
    private void firstCost2ChargingData(ChargingProcessParam chargingProcessParam,
                                        ChargingData chargingData,
                                        Integer hour,
                                        String name,
                                        BigDecimal powerFee) {
        //获取充电度数
        float chargingCapacity = chargingProcessParam.getChargingCapacity();
        //计算花费 = 每度电的价格 * 充电度数
        BigDecimal cost = getCost(chargingCapacity, powerFee);
        log.debug("当前时间（小时）：{}，计价规则名称：{}，充电度数：{}，电费单价：{}，本次花费：{}",
                hour, name, chargingCapacity, powerFee, cost);
        //保存当前总花费到chargingData
        chargingData.setTotalCost(cost);
        //保存当前充电记录到chargingData，为了下一次的计算做准备
        chargingData.setLastChargingProcess(chargingProcessParam);
    }

    /**
     * 第N次的充电同步的数据Param到ChargingData
     */
    private void lastCost2ChargingData(ChargingProcessParam param,
                                       ChargingData chargingData,
                                       Integer hour,
                                       String name,
                                       BigDecimal powerFee) {
        float realChargingCapacity = getRealChargingCapacity(param, chargingData);
        //计算花费 = 每度电的价格 * 充电度数
        BigDecimal cost = getCost(realChargingCapacity, powerFee);
        log.debug("当前时间（小时）：{}，计价规则名称：{}，充电度数：{}，电费单价：{}，本次花费：{}",
                hour, name, realChargingCapacity, powerFee, cost);
        //取上次的花费
        BigDecimal totalCost = chargingData.getTotalCost();
        //累加 当前总花费= 上次花费+本次花费
        BigDecimal currentTotalCost = totalCost.add(cost);
        //保存当前总花费到chargingData
        chargingData.setTotalCost(currentTotalCost);
        log.debug("当前时间（小时）：{}，计价规则名称：{}，充电度数：{}，电费单价：{}，本次花费：{},总花费{}",
                hour, name, realChargingCapacity, powerFee, cost, currentTotalCost);
        //保存当前充电记录到chargingData,为下次计算做准备
        chargingData.setLastChargingProcess(param);
    }


    /**
     * 获取真实的充电度数
     *
     * @param
     * @return
     */
    private float getRealChargingCapacity(ChargingProcessParam chargingProcessParam, ChargingData chargingData) {
        //上次充电记录
        ChargingProcessParam lastChargingProcess = chargingData.getLastChargingProcess();
        //上传充电的度数
        float chargingCapacity = lastChargingProcess.getChargingCapacity();
        //取这次充电度数
        float currentChargingCapacity = chargingProcessParam.getChargingCapacity();
        //因为身边同步到充电度数是包含关系，一共充了多少度电
        //实际充电的度数
        return currentChargingCapacity - chargingCapacity;

    }

    /**
     * 通过订单号获取充电数据
     *
     * @param param 参数
     * @return chargingData充电数据
     */
    private ChargingData getChargingData(ChargingProcessParam param) {
        //从map获取充电数据
        ChargingData chargingData = chargingProcessData.get(param.getOrderNo());
        //判断充电数据是否为空
        if (chargingData == null) {
            //充电数据为空，初始化一个充电数据
            chargingData = initChargingData(param);
        }
        return chargingData;
    }

    /**
     * 第一次同步充电数据
     * 创建数据同步记录对象 ChargingData
     * 设置 订单信息 设备信息 用户信息
     *
     * @param param
     * @return
     */
    private ChargingData initChargingData(ChargingProcessParam param) {
        ChargingData chargingData = new ChargingData();
        chargingData.setCount(1);
        chargingData.setOrderNo(param.getOrderNo());
        chargingData.setUserId(param.getUserId());
        chargingData.setGunId(param.getGunId());
        chargingData.setStartTime(LocalDateTime.now());
        //保存初始化数据到map
        chargingProcessData.put(param.getOrderNo(), chargingData);
        return chargingData;
    }

    /**
     * 计算价格  度数*单价
     *
     * @param chargingCapacity 充电度数
     * @param powerFee         每度电的价格
     * @return 价格
     */
    private BigDecimal getCost(float chargingCapacity, BigDecimal powerFee) {
        //将float类型的充电度数转为字符串类型
        String stringChargingCapacity = Float.toString(chargingCapacity);
        //把字符串类型的充电度数转换为BigDecimal
        BigDecimal bigDecimalChargingCapacity = new BigDecimal(stringChargingCapacity);
        //计算花费
        return powerFee.multiply(bigDecimalChargingCapacity);
    }

    /**
     * 通过时间和场站id去匹配计价规则
     *
     * @param stationId 场站id
     * @param hour      时间
     * @return 返回匹配的计价规则
     */
    private CostRulePO getMatchCostRuleByHour(Integer stationId, int hour) {
        log.debug("通过场站id：{}，获取计价规则列表", stationId);
        List<CostRulePO> costRulePOS = getCostRules(stationId);
        log.debug("通过场站id：{}，获取计价规则列表,结果：{}", stationId, costRulePOS);


        for (CostRulePO costRulePO : costRulePOS) {
            if (hour >= costRulePO.getStartTime() && hour < costRulePO.getEndTime()) {
                return costRulePO;
            }
        }
        return null;
    }


    @Autowired
    private CostRuleRepository costRuleRepository;

    @Autowired
    private CostRuleCacheRepository costRuleCacheRepository;

    /**
     * 通过场站信息获取计价规则
     * 计价规则是存储在数据库的会被高频的读取
     * 计价规则数据的特点
     * 1 会被高频读取
     * 2 不会频繁的变化
     * 这两种特点的数据 适合放入到redis
     * 库存可以放到redis中么？
     * 1 会被高频读取？  会
     * 2 频繁的变化么？  会
     * <p>
     * 普通电商的库存数据不建议放在redis中
     * 1 redis 不支持事务 支持不太行 相对应mysql
     * 2 库存在数据库存一份  如果放到redis中需要数据库和redis保持一致 非常难搞
     * <p>
     * 秒杀价格
     * 库存放入到redis 选择了高性能忽略了一致性和事务
     *
     * @param stationId 场站id
     * @return 返回场站计价规则列表
     */
    private List<CostRulePO> getCostRules(Integer stationId) {
        log.debug("通过场站id：{}，从redis获取计价规则", stationId);
        List<CostRulePO> cacheCostRules = costRuleCacheRepository.getCostRuleStationId(stationId);
        log.debug("通过场站id：{}，从redis获取计价规则：{}", stationId, cacheCostRules);
        //判断redis中是否存在数据
        if (!CollectionUtils.isEmpty(cacheCostRules)) {
            log.debug("通过场站id：{}，从redis获取计价规则,redis有数据，直接返回", stationId);
            return cacheCostRules;
        } else {
            log.debug("通过场站id：{}，从redis获取计价规则,redis无数据，从数据库查询", stationId);
            List<CostRulePO> dbCostRules = costRuleRepository.getCostRules(stationId);
            //判断数据库是否有数据
            if (!CollectionUtils.isEmpty(dbCostRules)) {
                log.debug("通过场站id：{}，从数据库获取计价规则,数据库有数据，写入redis并返回", stationId);
                costRuleCacheRepository.saveCostRule(stationId,dbCostRules);
                return dbCostRules;
            }
        }
        return Collections.emptyList();
    }
}
